package com.xbk.seckill.application.service.impl;

import cn.hutool.core.util.BooleanUtil;
import com.alibaba.fastjson.JSONObject;
import com.xbk.seckill.common.cache.distribute.DistributedCacheService;
import com.xbk.seckill.common.cache.local.LocalCacheService;
import com.xbk.seckill.common.cache.model.SeckillBusinessCache;
import com.xbk.seckill.common.constants.SeckillConstants;
import com.xbk.seckill.common.exception.ErrorCode;
import com.xbk.seckill.common.exception.SeckillException;
import com.xbk.seckill.common.model.dto.SeckillActivityDTO;
import com.xbk.seckill.common.model.dto.SeckillGoodsDTO;
import com.xbk.seckill.common.model.enums.SeckillGoodsStatus;
import com.xbk.seckill.common.model.message.ErrorMessage;
import com.xbk.seckill.common.model.message.TxMessage;
import com.xbk.seckill.common.utils.beans.BeanUtil;
import com.xbk.seckill.common.utils.id.SnowFlakeFactory;
import com.xbk.seckill.common.utils.time.SystemClock;
import com.xbk.seckill.dubbo.interfaces.activity.SeckillActivityDubboService;
import com.xbk.seckill.application.builder.SeckillGoodsBuilder;
import com.xbk.seckill.application.cache.SeckillGoodsCacheService;
import com.xbk.seckill.application.cache.SeckillGoodsListCacheService;
import com.xbk.seckill.application.command.SeckillGoodsCommond;
import com.xbk.seckill.application.service.SeckillGoodsService;
import com.xbk.seckill.goods.domain.model.entity.SeckillGoods;
import com.xbk.seckill.goods.domain.service.SeckillGoodsDomainService;
import com.xbk.seckill.mq.MessageSenderService;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboReference;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * 商品服务
 *
 * @author xiexu
 * @date 2025/1/12 12:23
 */
@Slf4j
@Service
public class SeckillGoodsServiceImpl implements SeckillGoodsService {

    @Autowired
    private SeckillGoodsDomainService seckillGoodsDomainService;

    /**
     * 禁用服务启动时的依赖检查。默认 check=true，若提供方服务未就绪，消费者启动会报错；
     * 设为 false 可跳过检查，允许消费者先启动，待提供方上线后自动重连
     */
    @DubboReference(version = "1.0.0", check = false)
    private SeckillActivityDubboService seckillActivityDubboService;

    @Autowired
    private SeckillGoodsListCacheService seckillGoodsListCacheService;

    @Autowired
    private SeckillGoodsCacheService seckillGoodsCacheService;

    @Autowired
    private DistributedCacheService distributedCacheService;

    @Autowired
    private LocalCacheService<String, SeckillGoods> localCacheService;

    @Autowired
    private MessageSenderService messageSenderService;

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void saveSeckillGoods(SeckillGoodsCommond seckillGoodsCommond) {
        if (seckillGoodsCommond == null) {
            throw new SeckillException(ErrorCode.PARAMS_INVALID);
        }
        // 查询秒杀活动
        SeckillActivityDTO seckillActivity = seckillActivityDubboService.getSeckillActivity(seckillGoodsCommond.getActivityId(), seckillGoodsCommond.getVersion());
        if (seckillActivity == null || seckillActivity.isEmpty()){
            throw new SeckillException(ErrorCode.ACTIVITY_NOT_EXISTS);
        }
        if (seckillActivity.isFallback()){
            throw new SeckillException(ErrorCode.SENTINEL_EXCEPTION);
        }
        SeckillGoods seckillGoods = SeckillGoodsBuilder.toSeckillGoods(seckillGoodsCommond);
        seckillGoods.setStartTime(seckillActivity.getStartTime());
        seckillGoods.setEndTime(seckillActivity.getEndTime());
        seckillGoods.setAvailableStock(seckillGoodsCommond.getInitialStock());
        seckillGoods.setId(SnowFlakeFactory.getSnowFlakeFromCache().nextId());
        seckillGoods.setStatus(SeckillGoodsStatus.PUBLISHED.getCode());
        // 保存秒杀商品信息到db
        boolean success = seckillGoodsDomainService.saveSeckillGoods(seckillGoods);
        // 如果落db成功，则存入缓存
        if (success) {
            // 将商品的库存同步到Redis
            distributedCacheService.put(SeckillConstants.getKey(SeckillConstants.GOODS_ITEM_STOCK_KEY_PREFIX, String.valueOf(seckillGoods.getId())), seckillGoods.getAvailableStock());
            // 商品限购同步到Redis
            distributedCacheService.put(SeckillConstants.getKey(SeckillConstants.GOODS_ITEM_LIMIT_KEY_PREFIX, String.valueOf(seckillGoods.getId())), seckillGoods.getLimitNum());
        }
    }

    @Override
    public SeckillGoods getSeckillGoodsId(Long id) {
        // 实现多级缓存，先从本地缓存获取
        String key = SeckillConstants.getKey(SeckillConstants.GOODS_ITEM_KEY_PREFIX, String.valueOf(id));
        //从本地缓存获取数据
        SeckillGoods seckillGoods = localCacheService.getIfPresent(key);
        // 如果本地缓存为空，则从Redis中获取数据
        if (Objects.isNull(seckillGoods)) {
            // 从Redis获取数据
            seckillGoods = distributedCacheService.getObject(key, SeckillGoods.class);
            // 如果从Redis获取的数据为空
            if (Objects.isNull(seckillGoods)) {
                // 则从数据库中获取数据
                seckillGoods = seckillGoodsDomainService.getSeckillGoodsId(id);
                if (seckillGoods != null) {
                    // 将数据缓存到Redis
                    distributedCacheService.put(key, seckillGoods, 10, TimeUnit.MINUTES);
                }
            }
            // 如果从数据库中获取到数据，则将数据缓存到本地缓存
            if (seckillGoods != null) {
                localCacheService.put(key, seckillGoods);
            }
        }
        return seckillGoods;
    }

    @Override
    public List<SeckillGoods> getSeckillGoodsByActivityId(Long activityId) {
        return seckillGoodsDomainService.getSeckillGoodsByActivityId(activityId);
    }

    /**
     * 更新商品状态
     *
     * @param status
     * @param id
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void updateStatus(Integer status, Long id) {
        if (Objects.equals(status, SeckillGoodsStatus.OFFLINE.getCode())) {
            // 清空缓存
            this.clearCache(String.valueOf(id));
        }
        seckillGoodsDomainService.updateStatus(status, id);
    }

    /**
     * 清空缓存的商品数据
     */
    private void clearCache(String id) {
        // 清除Redis缓存中的商品库存
        distributedCacheService.delete(SeckillConstants.getKey(SeckillConstants.GOODS_ITEM_STOCK_KEY_PREFIX, id));
        // 清除本地缓存中的商品信息
        distributedCacheService.delete(SeckillConstants.getKey(SeckillConstants.GOODS_ITEM_KEY_PREFIX, id));
        // 清除商品的限购信息
        distributedCacheService.delete(SeckillConstants.getKey(SeckillConstants.GOODS_ITEM_LIMIT_KEY_PREFIX, id));
        // 清除商品的限购信息
        distributedCacheService.delete(SeckillConstants.getKey(SeckillConstants.GOODS_ITEM_LIMIT_KEY_PREFIX, id));
    }

    /**
     * 扣减数据库库存
     *
     * @param count
     * @param id
     * @return
     */
    @Override
    public boolean updateAvailableStock(Integer count, Long id) {
        return seckillGoodsDomainService.updateAvailableStock(count, id);
    }

    /**
     * 扣减数据库库存
     *
     * @param count 扣减的库存数量
     * @param id    商品id
     * @return
     */
    @Override
    public boolean updateDbAvailableStock(Integer count, Long id) {
        return seckillGoodsDomainService.updateDbAvailableStock(count, id);
    }

    @Override
    public Integer getAvailableStockById(Long id) {
        return seckillGoodsDomainService.getAvailableStockById(id);
    }

    @Override
    public List<SeckillGoodsDTO> getSeckillGoodsList(Long activityId, Long version) {
        if (activityId == null) {
            throw new SeckillException(ErrorCode.ACTIVITY_NOT_EXISTS);
        }
        // 从缓存中获取数据
        SeckillBusinessCache<List<SeckillGoods>> seckillGoodsListCache = seckillGoodsListCacheService.getCachedGoodsList(activityId, version);
        // 活动不存在
        if (!seckillGoodsListCache.isExist()) {
            throw new SeckillException(ErrorCode.ACTIVITY_NOT_EXISTS);
        }
        // 稍后再试，前端需要对这个状态做特殊处理，即不去刷新数据，静默稍后再试
        if (seckillGoodsListCache.isRetryLater()) {
            throw new SeckillException(ErrorCode.RETRY_LATER);
        }
        return seckillGoodsListCache.getData().stream().map((seckillGoods) -> {
            SeckillGoodsDTO seckillGoodsDTO = new SeckillGoodsDTO();
            BeanUtil.copyProperties(seckillGoods, seckillGoodsDTO);
            seckillGoodsDTO.setVersion(seckillGoodsListCache.getVersion());
            return seckillGoodsDTO;
        }).collect(Collectors.toList());
    }

    /**
     * 根据id和版本号获取商品详情
     *
     * @param id
     * @param version
     * @return
     */
    @Override
    public SeckillGoodsDTO getSeckillGoods(Long id, Long version) {
        if (id == null) {
            throw new SeckillException(ErrorCode.PARAMS_INVALID);
        }
        // 获取商品信息
        SeckillBusinessCache<SeckillGoods> seckillGoodsCache = seckillGoodsCacheService.getSeckillGoods(id, version);
        // 缓存中不存在商品数据
        if (!seckillGoodsCache.isExist()) {
            throw new SeckillException(ErrorCode.ACTIVITY_NOT_EXISTS);
        }
        // 稍后再试，前端需要对这个状态做特殊处理，即不去刷新数据，静默稍后再试
        if (seckillGoodsCache.isRetryLater()) {
            throw new SeckillException(ErrorCode.RETRY_LATER);
        }
        SeckillGoodsDTO seckillGoodsDTO = SeckillGoodsBuilder.toSeckillGoodsDTO(seckillGoodsCache.getData());
        seckillGoodsDTO.setVersion(SystemClock.millisClock().now());
        return seckillGoodsDTO;
    }

    /**
     * 扣减数据库库存
     *
     * @param txMessage
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean updateAvailableStock(TxMessage txMessage) {
        // 组装商品事务key
        String goodsTxKey = SeckillConstants.getKey(SeckillConstants.GOODS_TX_KEY, String.valueOf(txMessage.getTxNo()));
        // 幂等判断
        Boolean decrementStock = distributedCacheService.hasKey(goodsTxKey);
        if (BooleanUtil.isTrue(decrementStock)) {
            log.info("updateAvailableStock|秒杀商品微服务已经扣减过数据库库存|{}", txMessage.getTxNo());
            return true;
        }
        // 是否成功扣减数据库库存的标志
        boolean isUpdate = false;
        try {
            // 扣减数据库商品库存，如果成功扣减则返回true
            isUpdate = seckillGoodsDomainService.updateAvailableStock(txMessage.getQuantity(), txMessage.getGoodsId());
            // 成功扣减数据库库存
            if (isUpdate) {
                // 保存事务日志
                distributedCacheService.put(goodsTxKey, txMessage.getTxNo(), SeckillConstants.TX_LOG_EXPIRE_DAY, TimeUnit.DAYS);
            } else {
                // 扣减数据库库存失败，发送失败消息给订单微服务
                ErrorMessage errorMessage = getErrorMessage(txMessage);
                messageSenderService.send(errorMessage);
            }
        } catch (Exception e) {
            isUpdate = false;
            log.error("updateAvailableStock|抛出异常|{},{}",txMessage.getTxNo(), e.getMessage());
            // 发送失败消息给订单微服务
            ErrorMessage errorMessage = getErrorMessage(txMessage);
            messageSenderService.send(errorMessage);
        }
        return isUpdate;
    }

    @Override
    public SeckillBusinessCache<Integer> getAvailableStock(Long goodsId, Long version) {
        return seckillGoodsCacheService.getAvailableStock(goodsId, version);
    }

    /**
     * 发送给订单微服务的错误消息
     */
    private ErrorMessage getErrorMessage(TxMessage txMessage) {
        return new ErrorMessage(
                SeckillConstants.TOPIC_ERROR_MSG,
                txMessage.getTxNo(),
                txMessage.getGoodsId(),
                txMessage.getQuantity(),
                txMessage.getPlaceOrderType(),
                txMessage.getException(),
                txMessage.getBucketSerialNo(),
                txMessage.getUserId(),
                txMessage.getOrderTaskId()
        );
    }
}
